"""
opc-ua类: 实现工业机器交互
opc-ua安装使用方式：pip install opcua
"""

import threading
import time
from functools import wraps

from opcua import Client, ua, Node

from update_value import wincc_task


def read_lock_func(func):
    @wraps(func)
    def run(*args, **kwargs):
        with threading.Lock():
            res = func(*args, **kwargs)
        return res

    return run


def write_lock_func(func):
    @wraps(func)
    def run(*args, **kwargs):
        with threading.Lock():
            res = func(*args, **kwargs)
        return res

    return run


# 自定义回调函数
class SubHandler(object):
    def datachange_notification(self, node, val, attr):
        print("New data change event: ", node, "----", val)
        if not val:
            val = 0
        wincc_task(str(node), float(val))


class EnumType:
    uaBool = ua.VariantType.Boolean
    uaString = ua.VariantType.String
    uaInt16 = ua.VariantType.Int16
    uaInt32 = ua.VariantType.Int32
    uaUInt32 = ua.VariantType.UInt32
    uaFloat = ua.VariantType.Float
    uaByte = ua.VariantType.Byte


class OpcClient:
    """
    OPCUA Client class
    """

    def __init__(self, ip, port, sub_ids):
        """
        :param ip:  opc server ip
        :param port: opc server port
        :param sub_ids: 需要监听的node_id元组(node_id, node_id...)
        """
        self._is_connect = False
        self.ip = ip
        self.port = port
        self.sub_ids = sub_ids
        self.client = Client("opc.tcp://{}:{}".format(ip, port))
        self.first_connect()
        t = threading.Thread(target=self.check_alive_and_connect_daemon, args=(60,))
        t.daemon = True
        t.start()
        t.join()

    def disconnect(self):
        self.client.disconnect()
        print("client disconnect")

    def first_connect(self):
        tmp = self.__connect__()
        if tmp is False:
            time.sleep(15)
            self.first_connect()
        return True

    def __connect__(self):
        try:
            print(">>>>>>>>> now connect to opcua server >>>>>>>>>")
            self.client.connect()
            self.client.load_type_definitions()
        except:  # noqa
            self._is_connect = False
            print("the connection to opcua is disconnect")
            return False

        self._is_connect = True
        print("The opcua connection is created now")

        if self.sub_ids and len(self.sub_ids) > 0:
            try:
                node_ids = [self.client.get_node(_) for _ in self.sub_ids]
            except:  # noqa
                raise Exception("not this node, please check id!")

            handler = SubHandler()
            self.create_subscription(node_ids, handler)
        return True

    def create_subscription(self, node_ids, node_handler, time_out=100):
        """
        注册监听事件
        :param node_id: node_id,not Node!!!
        :param node_handler:
        :param time_out:
        :return:
        """

        try:
            sub = self.client.create_subscription(time_out, node_handler)
            sub.subscribe_data_change(node_ids)
        except:  # noqa
            pass

    def check_alive(self):
        try:
            self.client.send_hello()
        except BaseException:
            try:
                self.client.disconnect()
            except BaseException:
                pass
            self._is_connect = False
            return False
        self._is_connect = True
        return True

    def check_alive_and_connect(self):
        with threading.Lock():
            result = self.check_alive()
        if result is True:
            return True
        else:
            return self.__connect__()

    def check_alive_and_connect_daemon(self, timedelta):
        while True:
            self.check_alive_and_connect()
            time.sleep(timedelta)

    def get_node(self, node_id):
        """
        获取节点对象
        :param node_id:
        :return:
        """
        try:
            node = self.client.get_node(node_id)
            return node
        except: # noqa
            raise Exception(f"no this node {node_id}")

    @read_lock_func
    def read_by_node(self, node):
        """
        获取节点对象的值
        """
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        return node.get_value()

    @read_lock_func
    def read_by_node_id(self, node_id):
        """
        获取节点对象的值
        """
        node = self.client.get_node(node_id)
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        return node.get_value()

    @write_lock_func
    def write_by_node_id(self, node_id, value, ua_type):
        """
        在节点写入值
        :param node_id: 节点对象
        :param value: 写入的值
        :param ua_type: 写入值的ua类型,参照EnumType
        :return:
        """
        try:
            node = self.client.get_node(node_id)
        except Exception as e:
            print(f"写入 获取node:{node_id}失败：{e}")
            return False
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        try:
            node.set_attribute(ua.AttributeIds.Value,
                               ua.DataValue(variant=ua.Variant(value=value,
                                                               varianttype=ua_type)))
            return True
        except Exception as e:
            print(f"write error: {e}")
